﻿// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)

using System;
using System.Collections.Generic;

namespace Bravura.Native.PluginBase
{
	/// <summary>
	/// Base class for a voice in voiced instruments.
	/// </summary>
	public abstract class Voice
	{
		#region Fields

		#region Invariant Values

		/// <summary>
		/// Delta time for one sample.
		/// </summary>
		protected double _dt = 1.0 / 48000.0;

		/// <summary>
		/// Sample rate
		/// </summary>
		protected double _sr = 48000.0;

		#endregion

		#region Configuration Values

		/// <summary>
		/// The attack rate.
		/// </summary>
		protected double _attRate = 10.0;

		/// <summary>
		/// The release rate.
		/// </summary>
		protected double _relRate = 1.0;

		/// <summary>
		/// The distortion value.
		/// </summary>
		protected double _dist = 1.0;

		#endregion

		#region Note-On Values

		/// <summary>
		/// Specified sound value.
		/// </summary>
		protected double _sound = 0.0;

		/// <summary>
		/// Specified pitch (midi standard).
		/// </summary>
		protected double _pitch = 0.0;

		/// <summary>
		/// Ordinary frequency (cycles per second).
		/// </summary>
		protected double _freqOrd = 0.0;

		/// <summary>
		/// Maximum size of a waveform based on frequency and sample rate.
		/// </summary>
		protected int _size = 8;

		/// <summary>
		/// Maximum number of harmonics based on frequency and sample rate.
		/// </summary>
		protected int _maxn = 4;

		/// <summary>
		/// Specified "note-on" velocity.
		/// </summary>
		protected double _velocity = 0.0;

		/// <summary>
		/// Output amplitude for channel 0 based on panning.
		/// </summary>
		protected double _mix0 = Math.Sqrt(0.5);

		/// <summary>
		/// Output amplitude for channel 1 based on panning.
		/// </summary>
		protected double _mix1 = Math.Sqrt(0.5);

		#endregion

		#region Transient Values

		/// <summary>
		/// Voice is attacking.
		/// </summary>
		protected bool _attacking = false;

		/// <summary>
		/// Voice has fully attacked.
		/// </summary>
		protected bool _attacked = false;

		/// <summary>
		/// Voice is releasing.
		/// </summary>
		protected bool _releasing = false;

		/// <summary>
		/// Voice has fully released.
		/// </summary>
		protected bool _released = false;

		/// <summary>
		/// Current amplitude envelope.
		/// </summary>
		protected double _level = 0.0;

		/// <summary>
		/// Last generated sample value.
		/// </summary>
		protected double[] _sample = new double[2];

		#endregion

		#endregion

		/// <summary>
		/// Construct the voice. 
		/// </summary>
		protected Voice(double dt)
		{
			_dt = dt;
			_sr = 1.0 / dt;
		}

		/// <summary>
		/// Construct the voice.
		/// </summary>
		protected Voice(double dt, double attRate, double relRate, double dist) : this(dt)
		{
			Configure(attRate, relRate, dist);
		}

		/// <summary>
		/// Set common voice configuration values.
		/// </summary>
		protected void Configure(double attRate, double relRate, double dist)
		{
			_attRate = attRate;
			_relRate = relRate;
			_dist = dist;
		}

		/// <summary>
		/// Updates the amplitude envelope based on attacking or releasing state.
		/// </summary>
		protected void UpdateEnvelope()
		{
			// update voice envelope
			if (_attacking)
			{
				_level += _dt * _attRate;
				if (_level > 1.0)
				{
					_level = 1.0;
					_attacking = false;
					_attacked = true;
				}
			}
			else if (_releasing)
			{
				_level -= _dt * _relRate;
				if (_level < 0.0)
				{
					_level = 0.0;
					_releasing = false;
					_released = true;
				}
			}
		}

		/// <summary>
		/// Sets the mix values per the specified pan value.
		/// </summary>
		protected void SetPan(double value)
		{
			_mix0 = Math.Sqrt(1.0 - value);
			_mix1 = Math.Sqrt(value);
		}

		/// <summary>
		/// Update voice configuration values.
		/// </summary>
		public virtual void Update(double attRate, double relRate, double dist)
		{
			// initialize configuration values
			_attRate = attRate;
			_relRate = relRate;
			_dist = dist;
		}

		/// <summary>
		/// Initialize base note-on values.
		/// </summary>
		public virtual void NoteOn(double sound, double pitch, double velocity)
		{
			// initialize note-on values
			_sound = sound;
			_pitch = pitch;
			_freqOrd = 110.0 * Math.Pow(2.0, (_pitch - 45.0) / 12.0);
			_size = (int)(_sr / _freqOrd);
			_maxn = _size / 2;
			_velocity = velocity;

			// initialize transient values
			_attacking = true;
			_attacked = false;
			_releasing = false;
			_released = false;
		}

		/// <summary>
		/// Handle note-off event.
		/// </summary>
		public virtual void NoteOff()
		{
			// update transient values
			_attacking = false;
			_attacked = true;
			_releasing = true;
			_released = false;
		}

		/// <summary>
		/// Handle note-setting events.
		/// </summary>
		public virtual void NoteSetting(string name, string value)
		{
			//
		}

		/// <summary>
		/// Handle note-control events.
		/// </summary>
		public virtual void NoteControl(string name, double value)
		{
			if (0 == string.Compare(name, "pan", true))
			{
				SetPan(value);
			}
		}

		/// <summary>
		/// Gets a sample from the voice.
		/// </summary>
		public abstract double[] Sample();
	}
}
